package com.doliere.android.app.p2p.services;

import java.net.ServerSocket;
import java.util.Enumeration;
import java.util.Hashtable;

import com.doliere.android.app.p2p.listeners.P2pActionListener;
import com.doliere.android.app.p2p.listeners.P2pDnsResponseListener;
import com.doliere.android.app.p2p.listeners.P2pServiceDnsTextRecordListener;
import com.doliere.android.app.p2p.receivers.P2pBroadcastReceiver;
import com.doliere.android.app.p2p.serviceinfos.P2pServiceInfo;

import android.annotation.SuppressLint;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.wifi.p2p.WifiP2pDevice;
import android.net.wifi.p2p.WifiP2pInfo;
import android.net.wifi.p2p.WifiP2pManager;
import android.net.wifi.p2p.WifiP2pManager.Channel;
import android.net.wifi.p2p.nsd.WifiP2pDnsSdServiceRequest;
import android.net.wifi.p2p.nsd.WifiP2pServiceRequest;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.widget.Toast;

/**
 * 
 * @author doliere
 * This service is responsible for handling P2P operations
 * and writing in a ContentProvider that will deliever informations
 * on P2P informations to activities that need them.
 */

public class P2pService extends Service{
	
	static WifiP2pManager mP2pWifiManager; // Get an instance of the P2P wifi manager.
	static Channel mP2pWifiChannel; //Got after initializing an instance of Wifi manager.
	static P2pBroadcastReceiver mP2pBroadcastReceiver;
	static IntentFilter mIntentFilter; // Register the service for listening to intents.
	static boolean mP2pWifiState;
	
	
	//For handling threads in the service.
	private Looper mServiceLooper;
	private ServiceHandler mServiceHandler;
	
	//Communications.
	ServerSocket mServerSocket;	
	
	//Service discovery informations
	private static P2pActionListener mAddLocalServiceActionListener;
	
	private Hashtable<String, WifiP2pDevice> mSismappServiceDevices = null;
	private Hashtable<String, String> mSismappUsers = null;
	private Hashtable<String, Integer> mSismappDevicePorts = null;
	
	private static P2pDnsResponseListener mDnsResponseListener;
	private static P2pServiceDnsTextRecordListener mDnsTextRecordListener;
	private Hashtable<String, WifiP2pDevice> mSismappDnsDevices = null;
	
	//Service request listener
	private static P2pActionListener mServiceRequestActionListener;
	private WifiP2pDnsSdServiceRequest mP2pServiceRequest;
	
	private static P2pActionListener mServiceDiscoveringActionListener;
	
	//Connected Devices and Sockets.
	
	Hashtable<String, Boolean> mConnectedDevices;
	Hashtable<String, WifiP2pInfo> mConnectedDevicesInfos;
	/**
	 * Handler
	 */
	
	private  class ServiceHandler extends Handler {
		
		public ServiceHandler(Looper looper) {
	          super(looper);
	      }
	      @Override
	      public void handleMessage(Message msg) {
	          initializeP2pWifiService();
	          registerSismappService(); // Register service.
	          registerDnsTextAndResponseListeners();
	          startServiceRequestAndDiscovery();
	          //stopSelf(msg.arg1);
	      }
	}
	
	public P2pService(){
		//initializeP2pWifiService(); // Initialize Wifi Service. Register for service discovery.
		//Toast.makeText(getApplicationContext(), "Started", Toast.LENGTH_SHORT).show();
	}
	
	
	@Override
	public void onCreate(){
		super.onCreate();
		HandlerThread thread = new HandlerThread("SISMAPP SERVICE PROCESS", Process.THREAD_PRIORITY_BACKGROUND);
	    thread.start();
	    
	    mServiceLooper = thread.getLooper();
	    mServiceHandler = new ServiceHandler(mServiceLooper);
	}
	@Override
	public int onStartCommand(Intent intent, int arg1, int startId){
		//int id = super.onStartCommand(intent, arg1, startId);
		Toast.makeText(getApplicationContext(), "Service started", Toast.LENGTH_SHORT).show();
		
		Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        mServiceHandler.sendMessage(msg);
		return START_STICKY;
	}
	
	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return null;
	}
	
	@SuppressLint("NewApi")
	@Override
	public void onDestroy(){
		super.onDestroy();
		if(mP2pBroadcastReceiver != null && mP2pWifiChannel != null){
			unregisterReceiver(mP2pBroadcastReceiver);
			mP2pWifiManager.clearLocalServices(mP2pWifiChannel, mAddLocalServiceActionListener);
			mP2pWifiManager.clearServiceRequests(mP2pWifiChannel, mServiceRequestActionListener);
		}
		Toast.makeText(getApplicationContext(), "Service destroyed", Toast.LENGTH_SHORT).show();
	}
	
	/**
	 * @author doliere
	 * This section of the service is about setting up the P2p wifi services : a manager and a channel.
	 * And registring for peers discovery, getting peers list, establishing connections and exchanging datas...
	 */
	@SuppressLint("NewApi")
	public void initializeP2pWifiService(){
		mP2pWifiManager = (WifiP2pManager)getSystemService(WIFI_P2P_SERVICE);
		mP2pWifiChannel = mP2pWifiManager.initialize(getApplicationContext(), getMainLooper(), new WifiP2pManager.ChannelListener() {
			
			@Override
			public void onChannelDisconnected() {
				Toast.makeText(getApplicationContext(), "Channel disconnected", Toast.LENGTH_SHORT).show();
			}
		});
		
		mIntentFilter = new IntentFilter(); 
		mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
		mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
		mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
		mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
		mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION);
		
		mP2pBroadcastReceiver = new P2pBroadcastReceiver(mP2pWifiManager, mP2pWifiChannel, this);
		registerReceiver(mP2pBroadcastReceiver, mIntentFilter);
	}
	
	
	//This method is called from the broadcast receiver to set the wifi state
	public void setP2pWifiState(boolean state){
		mP2pWifiState = state;
	}
	
	/**
	 * This section is about registring the application for SISMAPP service discovery.
	 * So that peers can be filtered given that they provide or not a SISMAPP service.s
	 */
	
	@SuppressLint("NewApi")
	public void registerSismappService(){
		mAddLocalServiceActionListener = new P2pActionListener("ADD_LOCAL_SERVICE");
		mP2pWifiManager.addLocalService(mP2pWifiChannel, new P2pServiceInfo(this.getServerPort()+ "", "doliere").getServiceInfo(), mAddLocalServiceActionListener);
	}
	
	@SuppressLint("NewApi")
	public void registerDnsTextAndResponseListeners(){
		mDnsTextRecordListener = new P2pServiceDnsTextRecordListener(this);
		mDnsResponseListener = new P2pDnsResponseListener(this);
		mP2pWifiManager.setDnsSdResponseListeners(mP2pWifiChannel, mDnsResponseListener.getListener(), mDnsTextRecordListener.getListener());
	}
	//This method is called when the a new sismapp service device is discovered. 
	public void setSismappDevices(Hashtable<String, WifiP2pDevice> liste){
		if(mSismappServiceDevices != null && mSismappServiceDevices.size() > 0){
			mSismappServiceDevices.clear();
		}
		mSismappServiceDevices = liste;
	}
	
	@SuppressLint("NewApi")
	public void startServiceRequestAndDiscovery(){
		mServiceRequestActionListener = new P2pActionListener("SERVICE REQUEST");
		mServiceDiscoveringActionListener = new P2pActionListener("DISCOVER SERVICE");
		
		mP2pServiceRequest = WifiP2pDnsSdServiceRequest.newInstance(P2pServiceInfo.INSTANCE_NAME, P2pServiceInfo.mProtocolType);
		
		mP2pWifiManager.addServiceRequest(mP2pWifiChannel, mP2pServiceRequest, mServiceRequestActionListener);
		
		mP2pWifiManager.discoverServices(mP2pWifiChannel, mServiceDiscoveringActionListener);
	}
	
	
	//This method is called to associate a device MAC @ to the user that holds that device.
	public void setSismappUsers(Hashtable<String, String> users){
		if(mSismappUsers != null && mSismappUsers.size() > 0){
			mSismappUsers.clear();
		}
		mSismappUsers = users;
	}
	
	public void setSismappDevicesPort(Hashtable<String, Integer> ports){
		if(mSismappDevicePorts != null && mSismappDevicePorts.size() >0){
			mSismappDevicePorts.clear();
		}
		mSismappDevicePorts = ports;
	}
	
	public void setDnsDevices(Hashtable<String, WifiP2pDevice> mDnsDevices){
		
		if(mSismappDnsDevices != null && mSismappDnsDevices.size() >0){
			mSismappDnsDevices.clear();
		}
		
		mSismappDnsDevices = mDnsDevices;
	}
	
	public Hashtable<String, WifiP2pDevice> getDnsDevices(){
		return mSismappDnsDevices;
	}
	
	public void setConnectedDevices(Hashtable<String, Boolean> devicesConnected){
		if(devicesConnected != null && devicesConnected.size() != 0){
			Enumeration<String> devs = devicesConnected.keys();
			String devAdr = null;
			if(devs.hasMoreElements()){
				devAdr = devs.nextElement();
			}
			if(devicesConnected.get(devAdr) == true)
				mConnectedDevices.put(devAdr, true);
		}
	}
	
	public void setConnectedDevicesInfos(Hashtable<String, WifiP2pInfo> deviceInfos){
		Enumeration<String> devs = deviceInfos.keys();
		String adr = devs.nextElement();
		mConnectedDevicesInfos.put(adr, deviceInfos.get(adr));
	}
	/**
	 * This section is about handling sockets communications.
	 */
	
	/*
	 * If a group has been successfully formed, start a socket communication :
	 * Then, the groupowner creates a socket based on a serversocket(ss.accept() 
	 * and the the ! groupowner creates a simple socket with connect.
	 * All that in a new thread.
	 * Try to keep messages in a database, by creating a content Provider
	 */
	public void setNewConnectionInfos(WifiP2pInfo info){
		
	}
	
	
	/*
	 * In this method, we'll start a new thread that will issue a client 
	 * connection to another device which is a server, a groupowner.
	 */
	
	public void connectToAGroupOwner(Hashtable<String, WifiP2pInfo> deviceInfos){
		
	}
	
	
	/*
	 * This method is responsible for starting a socket listener to an incoming connection
	 * from another device which is not a group owner. Start a socket on the device socket.
	 */
	public void waitForAnIncomingConnection(Hashtable<String, WifiP2pInfo> deviceInfos){
		
	}
	public void setServerSocket(ServerSocket server){
		mServerSocket = server;
	}
	
	public ServerSocket getServerSocket(){
		return mServerSocket;
	}
	
	public int getServerPort(){
		return mServerSocket == null ? -1 : mServerSocket.getLocalPort();
	}
}
